/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#ifndef __PICKING_HPP_
#define __PICKING_HPP_

#include <map>
#include <GL/gl.h>
#include "sync.hpp"

class CGLPickable;

/** TGLPickEvent
 * 
 * Data container passed to callback object on picking events.
 */
struct TGLPickEvent
{
  CGLPickable *Pickable;  //< event's source
  int Button;             //< button that was pressed
  int ScreenX, ScreenY;   //< pointer's coordinates on window
  float GlX, GlY, GlZ;    //< pointer's coordinates in opengl
};

class CGLPickCallback
{
  public:
    CGLPickCallback(){}
    
    /** On focus
     * 
     * Called when pointer is on top of object.
     */
    virtual void OnFocus(TGLPickEvent event){}
    
    /** On focus
     * 
     * Called when pointer moves off object.
     */
    virtual void OnFocusLost(TGLPickEvent event){}
    
    /** On pick
     * 
     * Called when object is picked.
     */
    virtual void OnPick(TGLPickEvent event){}
    
    /** On pick
     * 
     * Called when object has been previously picked and then released.
     */
    virtual void OnRelease(TGLPickEvent event){}
    
    /** On drag
     * 
     * Called when pointer has started dragging on this object. In other words, it has been 
     * picked and pointer has moved before release.
     */
    virtual void OnDrag(TGLPickEvent event){}
    
};

class CGLPickable
{
  public:
    CGLPickable();
    
    /** Draw
     * 
     * Implement your drawing here. No need to load names to opengl's namestack 
     * here, just draw the pickable object.
     */
    virtual void DrawPickable();
    
    /** Set callback
     * 
     * Set the callback that receives picking events.
     */
    void SetCallback(CGLPickCallback *callback);
    
    /** Get callback
     * 
     * Returns the call back of this pickable.
     */
    CGLPickCallback *GetCallback();
    
    /** Set user data
     * 
     * Sets pickables user data field. Rember to clean the data yourself.
     */
    void SetUserData(void *data);
    
    /** Get user data
     * 
     * Get pickables user data.
     */
    void *GetUserData();
    
    /** Enable picking
     * 
     * Defines if picking actions by picking context(s) are applied on pickable.
     */
    void EnablePicking(bool enable);
    
    /** Is picking enabled?
     * 
     */
    bool IsPickingEnabled();
    
  private:
    CGLPickCallback *callback;
    void *userData;
    bool enabled;
};

#define SELECT_BUFFER_SIZE 128

/** CGLPickingContext
 * 
 * Handler for pickables. Call mouse events on context to apply them to pickables.
 * Context takes care of picking procedure and in case of hit event calling. 
 * Applying projections and assigning names are done by context and every pickable
 * just has to perform drawing of pickable object.
 * Picking context only performs select mode rendering.
 * 
 */
class CGLPickingContext : private gim::CSync
{
  public:
    CGLPickingContext();
    
    /** Set projection matrix
     * 
     * Set the OpenGL projection matrix (double array of 16 elements) to use in 
     * selection mode rendering.
     * 
     */
    void SetProjectionMatrix(double* projection);
    
    const double* GetProjectionMatrix();
    
    /** Set modelview matrix
     * 
     * Set the OpenGL modelview matrix (double array of 16 elements) to use in 
     * selection mode rendering.
     */
    void SetModelviewMatrix(double* modelview);
    
    const double* GetModelviewMatrix();
    
    /** On mouse down
     * 
     * Call this to apply mouse down event.
     */
    void OnMouseDown(const int x, const int y, const int button);
    
    /** On mouse up
     * 
     * Call this to apply mouse up event.
     */
    void OnMouseUp(const int x, const int y);
    
    /** On mouse move
     * 
     * Call this to apply mouse movement event.
     */
    void OnMouseMove(const int x, const int y);
        
    /** Add pickable
     * 
     * Adds a pickable to context. Returns true on succes, otherwise false.
     */
    bool AddPickable(CGLPickable *pickable);
    
    /** Remove pickable
     * 
     * Removes pickable from context.
     */
    void RemovePickable(CGLPickable *pickable);
    
    /** Enable focus events
     * 
     * Enables focus events for context. Focus events occur when pointer is 
     * brought on an object. Focus events require context to perform picking on
     * every mouse movement so disabling them might save some processing power 
     * on contexts with many objects.
     */
    void EnableFocusEvents(bool enable);
    
    bool FocusEventsEnabled();
    
  private:
    GLuint counter;
    std::map<GLuint, CGLPickable*> pickables;
    GLuint selectBuffer[SELECT_BUFFER_SIZE];
    
    bool pickActive;
    bool focusActive;
    TGLPickEvent lastEvent;
    
    bool focusEventsEnabled;
    
    float lastScreenZ;
    
    double projectionMatrix[16];
    double modelviewMatrix[16];
    
    CGLPickable *pick(const int x, const int y, float &glx, float &gly, float &glz);
    GLuint getTopmostHit(const GLint hits);
};

#endif
